const fp  = require('lodash/fp');

// curry
// function curry(fn) {
//     return function curried(...args) {
//         if (args.length < fn.length) {
//             return function () {
//                 return curried(...args.concat(Array.from(arguments)));
//             };
//         }
//
//         return fn(...args);
//     };
// }
//
// function sum(a, b, c) {
//     return a + b + c;
// }
//
// const c = curry(sum);
// console.log(c(1, 2, 3));
// console.log(c(1, 2)(3));
// console.log(c(1)(2)(3));


// compose
// function compose(...args) {
//     return function (value) {
//         return args.reduce((acc, fn) => {
//             return fn(acc);
//         }, value);
//     }
// }
//
// const c  = compose(fp.toUpper, fp.first);
// console.log(c('wrf_123'));

// memorize
// function memoize(fn) {
//     const cache = {};
//     return function () {
//         const key = JSON.stringify(arguments);
//         cache[key] = cache[key] ?? fn.apply(fn, Array.from(arguments));
//         return cache[key];
//     };
// }
//
// function calcCircle(r) {
//     console.log(1);
//     return Math.PI * r * r;
// }
//
// const m = memoize(calcCircle);
//
// console.log(m(4));
// console.log(m(4));
// console.log(m(5));

// cloneDeep
// function cloneDeep(obj) {
//     let res = null;
//     if (Array.isArray(obj)) {
//         res = [];
//     } else if (typeof obj === 'object') {
//         res = {};
//     } else {
//         return obj;
//     }
//
//     for (const key in obj) {
//         if (Array.isArray(obj[key]) || typeof obj[key] === 'object') {
//             res[key] = cloneDeep(obj[key]);
//         } else {
//             res[key] = obj[key]
//         }
//     }
//
//     return res;
// }
//
// const a = { a: 1 };
// const b = { b: a };
//
// const c = cloneDeep(b);
// c.b.a = 2;
// console.log(c.b.a);
// console.log(a.a);
//
// const d = Object.assign({}, b);
// d.b.a = 2;
// console.log(d.b.a)
// console.log(a.a)

// bind
// Function.prototype.myBind = function (context) {
//     const self = this;
//     const args = Array.prototype.slice.call(arguments);
//
//     const FB = function () {};
//
//     function FA() {
//         return self.apply(this instanceof FB ? this : context, args.concat(Array.from(arguments)));
//     }
//
//     if (this.prototype)
//         FB.prototype = this.prototype;
//
//     FA.prototype = new FB();
//
//     return FA;
// }
//
// console.log.bind(console, 1, 2, 3)();

// call
// Function.prototype.myCall = function (context) {
//     const ctx  = context || window;
//     ctx.fn = this;
//
//     const arr = []
//     for (let i = 1; i < arguments.length; i++) {
//         arr.push('arguments['+i+']');
//     }
//
//     const res = eval('context.fn('+arr+')')
//     delete context.fn;
//
//     return res;
// };
//
// console.log.call(console, 1,2,3)

// apply
// Function.prototype.myApply = function (context, arr) {
//     const ctx = context || window;
//     ctx.fn = this;
//
//     const array = [];
//     for (let i = 0; i < arr.length; i++) {
//         array.push('arr['+i+']');
//     }
//
//     const res = eval('ctx.fn('+array+')');
//     delete ctx.fn;
//
//     return res;
//
// };
//
// console.log.apply(this, [1,2,3]);

// new
// function newFactory() {
//     const obj = Object.create(null);
//     Constructor = [].shift.call(arguments);
//     obj.__proto__ = Constructor.prototype;
//     const ret = Constructor.apply(obj, arguments);
//
//     return typeof ret === 'object' ? ret : obj;
// }
//
// console.log(newFactory(Object, { a: 1 }));

// async/await
// function asyncFunction() {
//     return toGenerator(function* () {
//         // ...
//         return 1;
//     });
// }
//
// function toGenerator(genF) {
//     return new Promise((resolve, reject) => {
//         var gen = genF();
//         function step(nextF) {
//             try {
//                 var next = nextF();
//             } catch (e) {
//                 return reject(e);
//             }
//
//             if (next.done) {
//                 return resolve(next.value);
//             }
//
//             Promise.resolve(next.value).then((v) => {step(() => gen.next(v)) }, (e) => { step(() => gen.throw(e)) });
//         }
//
//         step(() => gen.next(undefined));
//     });
// }
//
// asyncFunction().then(v => console.log(v))

// function toGenerator(genF) {
//     return new Promise((resolve, reject) => {
//         var gen = genF();
//         function step(nextF) {
//             try {
//                 var next = nextF();
//             } catch (e) {
//                 return reject(e);
//             }
//             if (next.done) {
//                 return resolve(next.value);
//             }
//
//             Promise.resolve(next.value).then(v => step(() => gen.next(v)), e => step(() => gen.throw(e)));
//         }
//
//         step(() => gen.next(undefined));
//     });
// }
//
// function asyncFunction() {
//     return toGenerator(function* () {
//         // ...
//         return 1;
//     });
// }
//
// asyncFunction().then(console.log(1))

// promise
// const FULFILLED = 'fulfilled';
// const REJECTED = 'rejected';
// const PENDING = 'pending';
// class MyPromise {
//     status = PENDING;
//     value = undefined;
//     reason = undefined;
//     successCallback = [];
//     failCallback = [];
//
//     constructor(executor) {
//         try {
//             executor(this.resolve, this.reject);
//         } catch (e) {
//             this.reject(e);
//         }
//     }
//
//     resolve = (v) => {
//         if (this.status !== PENDING) return;
//         this.status = FULFILLED;
//         this.value = v;
//         while (this.successCallback.length > 0)
//             this.successCallback.shift()();
//     }
//
//     reject = (e) => {
//         if (this.status !== PENDING) return;
//         this.status = REJECTED;
//         this.reason = e;
//         while (this.failCallback.length > 0)
//             this.failCallback.shift()();
//     }
//
//     then = (successCallback, failCallback) => {
//         successCallback = typeof successCallback === 'function' ? successCallback : value => value;
//         failCallback = typeof failCallback === 'function' ? failCallback : e => { throw e };
//         const promise2 = new MyPromise((resolve, reject) => {
//             if (this.status === FULFILLED) {
//                 setTimeout(() => {
//                     try {
//                         const x = successCallback(this.value);
//                         resolvePromise(x, promise2, resolve, reject);
//                     } catch (e) {
//                         reject(e);
//                     }
//                 }, 0);
//             } else if (this.status === REJECTED) {
//                 setTimeout(() => {
//                     try {
//                         const x = failCallback(this.reason);
//                         resolvePromise(x, promise2, resolve, reject);
//                     } catch (e) {
//                         reject(e);
//                     }
//                 }, 0);
//             } else {
//                 this.successCallback.push(() => {
//                     setTimeout(() => {
//                         try {
//                             const x = successCallback(this.value);
//                             resolvePromise(x, promise2, resolve, reject);
//                         } catch (e) {
//                             reject(e);
//                         }
//                     }, 0);
//                 });
//                 this.failCallback.push(() => {
//                     setTimeout(() => {
//                         try {
//                             const x = failCallback(this.reason);
//                             resolvePromise(x, promise2, resolve, reject);
//                         } catch (e) {
//                             reject(e);
//                         }
//                     }, 0);
//                 });
//             }
//         })
//         return promise2;
//     }
//
//     catch (failCallback) {
//         return this.then(undefined, failCallback);
//     }
//
//     finally (callback) {
//         return this.then((value) => {
//             return MyPromise.resolve(callback()).then(() => value, undefined);
//         }, (e) => {
//             return MyPromise.reject(callback()).then(undefined, () => { throw e });
//         });
//     }
//
//     static resolve(x) {
//         if (x instanceof MyPromise) return x;
//         return new MyPromise((resolve) => { resolve(x); })
//     }
//
//     static reject(e) {
//         if (e instanceof MyPromise) return e;
//         return new MyPromise((resolve, reject) => { reject(e) });
//     }
//
//     static all(array) {
//         return new MyPromise((resolve, reject) => {
//             let result = [];
//             let index = 0;
//             function addData(i, data) {
//                 result[i] = data;
//                 index ++;
//                 if (index === array.length)
//                     resolve(result);
//             }
//             for (let i = 0; i < array.length; i++) {
//                 if (array[i] instanceof MyPromise) {
//                     array[i].then(value => {
//                         addData(i, value);
//                     }, e => { reject(e) });
//                 } else {
//                     addData(i, array[i]);
//                 }
//             }
//         });
//     }
// }
//
// function resolvePromise(x, promise2, resolve, reject) {
//     if (x === promise2) {
//         reject(new TypeError('...'));
//         return;
//     }
//     if (x instanceof MyPromise) {
//         x.then(resolve, reject);
//     } else {
//         resolve(x);
//     }
// }
//
// const a = () => new MyPromise((resolve, reject) => {
//     resolve(111);
// });
//
// Promise.all([a(), 1, 2, a()]).then(res => {
//     console.log(res);
// })